/**
 * @class Ext.grid.RowSelectionModel
 * @extends Ext.AbstractStoreSelectionModel
 * 
 * Implement row based navigation via keyboard.
 *
 * Must synchronize across grid sections
 */
Ext.define('Ext.grid.RowSelectionModel', {
    extend: 'Ext.selection.Model',
    alias: 'selection.rowselectionmodel',
    requires: ['Ext.util.KeyNav'],
    
    /**
     * @private
     * Number of pixels to scroll to the left/right when pressing
     * left/right keys.
     */
    deltaScroll: 5,
    
    /**
     * @cfg {Boolean} enableKeyNav
     * Turns on/off keyboard navigation within the grid. Defaults to true.
     */
    enableKeyNav: true,

    bindComponent: function(view) {
        this.views = this.views || [];
        this.views.push(view);
        this.bind(view.getStore(), true);

        view.on({
            refresh: this.refresh,
            rowupdated: this.onRowUpdated,
            click: this.onRowMouseDown,
            scope: this
        });

        if (this.enableKeyNav) {
            this.initKeyNav(view);
        }
    },

    initKeyNav: function(view) {
        if (!view.rendered) {
            view.on('render', Ext.Function.bind(this.initKeyNav, this, [view], 0), this, {single: true});
            return;
        }

        view.el.set({
            tabIndex: -1
        });

        // view.el has tabIndex -1 to allow for
        // keyboard events to be passed to it.
        this.keyNav = new Ext.util.KeyNav(view.el, {
            "up": this.onKeyUp,
            "down": this.onKeyDown,
            "right": this.onKeyRight,
            "left": this.onKeyLeft,
            "pageDown": this.onKeyPageDown,
            "pageUp": this.onKeyPageUp,
            "home": this.onKeyHome,
            "end": this.onKeyEnd,
            scope: this
        });
        view.el.on(Ext.EventManager.getKeyEvent(), this.onKeyPress, this);
    },

    // Returns the number of rows currently visible on the screen or
    // false if there were no rows. This assumes that all rows are
    // of the same height and the first view is accurate.
    getRowsVisible: function() {
        var rowsVisible = false,
            view = this.views[0],
            row = view.getNode(0),
            rowHeight, gridViewHeight;

        if (row) {
            rowHeight = Ext.fly(row).getHeight();
            gridViewHeight = view.el.getHeight();
            rowsVisible = Math.floor(gridViewHeight/rowHeight);
        }

        return rowsVisible;
    },

    // go to last visible record in grid.
    onKeyEnd: function(e, t) {
        var last = this.store.getAt(this.store.getCount() - 1);
        if (last) {
            if (e.shiftKey) {
                this.selectRange(last, this.lastFocused || 0);
                this.setLastFocused(last);
            } else if (e.ctrlKey) {
                this.setLastFocused(last);
            } else {
                this.doSelect(last);
            }
        }
    },

    // go to first visible record in grid.
    onKeyHome: function(e, t) {
        var first = this.store.getAt(0);
        if (first) {
            if (e.shiftKey) {
                this.selectRange(first, this.lastFocused || 0);
                this.setLastFocused(first);
            } else if (e.ctrlKey) {
                this.setLastFocused(first);
            } else {
                this.doSelect(first, false);
            }
        }
    },

    // Go one page up from the lastFocused record in the grid.
    onKeyPageUp: function(e, t) {
        var rowsVisible = this.getRowsVisible();
        if (rowsVisible) {
            var selIdx = this.lastFocused ? this.store.indexOf(this.lastFocused) : 0;
            var prevIdx = selIdx - rowsVisible;
            if (prevIdx < 0) {
                prevIdx = 0;
            }
            var prevRecord = this.store.getAt(prevIdx);
            if (e.shiftKey) {
                var currRec = this.store.getAt(selIdx);
                this.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
                this.setLastFocused(prevRecord);
            } else if (e.ctrlKey) {
                e.preventDefault();
                this.setLastFocused(prevRecord);
            } else {
                this.doSelect(prevRecord);
            }

        }
    },

    // Go one page down from the lastFocused record in the grid.
    onKeyPageDown: function(e, t) {
        var rowsVisible = this.getRowsVisible();
        if (rowsVisible) {
            var selIdx = this.lastFocused ? this.store.indexOf(this.lastFocused) : 0;
            var nextIdx = selIdx + rowsVisible;
            if (nextIdx >= this.store.getCount()) {
                nextIdx = this.store.getCount() - 1;
            }
            var nextRecord = this.store.getAt(nextIdx);
            if (e.shiftKey) {
                var currRec = this.store.getAt(selIdx);
                this.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
                this.setLastFocused(nextRecord);
            } else if (e.ctrlKey) {
                // some browsers, this means go thru browser tabs
                // attempt to stop.
                e.preventDefault();
                this.setLastFocused(nextRecord);
            } else {
                this.doSelect(nextRecord);
            }
        }
    },

    // Select/Deselect based on pressing Spacebar.
    // Assumes a SIMPLE selectionmode style
    onKeyPress: function(e, t) {
        if (e.getKey() === e.SPACE) {
            e.stopEvent();
            var record = this.lastFocused;
            if (record) {
                if (this.isSelected(record)) {
                    this.doDeselect(record, false);
                } else {
                    this.doSelect(record, true);
                }
            }
        }
    },

    // Navigate one record up. This could be a selection or
    // could be simply focusing a record for discontiguous
    // selection. Provides bounds checking.
    onKeyUp: function(e, t) {
        var view = this.views[0],
            idx  = this.store.indexOf(this.lastFocused);
        if (idx > 0) {
            // needs to be the filtered count as thats what
            // will be visible.
            var record = this.store.getAt(idx - 1);
            if (e.shiftKey && this.lastFocused) {
                if (this.isSelected(this.lastFocused) && this.isSelected(record)) {
                    this.doDeselect(this.lastFocused, true);
                    this.setLastFocused(record);
                } else if (!this.isSelected(this.lastFocused)) {
                    this.doSelect(this.lastFocused, true);
                    this.doSelect(record, true);
                } else {
                    this.doSelect(record, true);
                }
            } else if (e.ctrlKey) {
                this.setLastFocused(record);
            } else {
                this.doSelect(record);
                view.focusRow(idx - 1);
            }
        } else if (this.selected.getCount() == 0) {
            this.doSelect(record);
            view.focusRow(idx - 1);
        }
    },

    // Navigate one record down. This could be a selection or
    // could be simply focusing a record for discontiguous
    // selection. Provides bounds checking.
    onKeyDown: function(e, t) {
        var view = this.views[0],
            idx  = this.store.indexOf(this.lastFocused);
        // needs to be the filtered count as thats what
        // will be visible.
        if (idx + 1 < this.store.getCount()) {
            var record = this.store.getAt(idx + 1);
            if (this.selected.getCount() == 0) {
                this.doSelect(record);
                view.focusRow(idx + 1);
            } else if (e.shiftKey && this.lastFocused) {
                if (this.isSelected(this.lastFocused) && this.isSelected(record)) {
                    this.doDeselect(this.lastFocused, true);
                    this.setLastFocused(record);
                } else if (!this.isSelected(this.lastFocused)) {
                    this.doSelect(this.lastFocused, true);
                    this.doSelect(record, true);
                } else {
                    this.doSelect(record, true);
                }
            } else if (e.ctrlKey) {
                this.setLastFocused(record);
            } else {
                this.doSelect(record);
                view.focusRow(idx + 1);
            }
        }
    },
    
    scrollByDeltaX: function(delta) {
        var view    = this.views[0],
            grid    = view.up('gridpanel'),
            hScroll = grid.down('gridscroller[dock=bottom]');
            
        hScroll.scrollByDeltaX(delta);
    },
    
    onKeyLeft: function(e, t) {
        this.scrollByDeltaX(-this.deltaScroll);
    },
    
    onKeyRight: function(e, t) {
        this.scrollByDeltaX(this.deltaScroll);
    },



    // Select the record with the event included so that
    // we can take into account ctrlKey, shiftKey, etc
    onRowMouseDown: function(view, rowIdx, node, e) {
        view.el.focus();
        var record = view.getStore().getAt(rowIdx);
        this.selectWithEvent(record, e);
    },

    // row has been repainted, lets maintain selection
    onRowUpdated: function(view, rowIdx, record) {
        if (this.isSelected(record)) {
            view.onRowSelect(rowIdx);
        }
    },

    // Allow the GridView to update the UI by
    // adding/removing a CSS class from the row.
    onSelectChange: function(record, isSelected) {
        var views   = this.views,
            viewsLn = views.length,
            store   = this.store,
            rowIdx  = store.indexOf(record),
            i = 0;
            
        for (; i < viewsLn; i++) {
            if (isSelected) {
                views[i].onRowSelect(rowIdx);
            } else {
                views[i].onRowDeselect(rowIdx);
            }
        }
    },

    // Provide indication of what row was last focused via
    // the gridview.
    onLastFocusChanged: function(oldFocused, newFocused) {
        var views   = this.views,
            viewsLn = views.length,
            store   = this.store,
            rowIdx,
            i = 0;

        rowIdx = store.indexOf(oldFocused);
        if (rowIdx != -1) {
            for (; i < viewsLn; i++) {
                views[i].onRowFocus(rowIdx, false);
            }
        }

        rowIdx = store.indexOf(newFocused);
        if (rowIdx != -1) {
            for (i = 0; i < viewsLn; i++) {
                views[i].onRowFocus(rowIdx, true);
            }
        }
    }
});